// Package failover
/**
* @Project : geektime-basic-go-study
* @File    : service.go
* @IDE     : GoLand
* @Author  : Tvux
* @Date    : 2024/10/22 09:46
**/

package failover

import (
	"context"
	"errors"
	"geektime-basic-go-study/webook/backend/internal/service/sms"
	"geektime-basic-go-study/webook/backend/internal/service/sms/ratelimit"
	"log"
	"sync/atomic"
)

type SMSFailoverService struct {
	svcs []sms.Service

	idx uint64
}

func (Self *SMSFailoverService) Send(ctx context.Context, tplId string, args []string, phones ...string) error {
	//return Self.SendV1(ctx, tplId, args, phones...)
	return Self.SendV2(ctx, tplId, args, phones...)
}

func (Self *SMSFailoverService) SendV1(ctx context.Context, tplId string, args []string, phones ...string) error {
	for _, svc := range Self.svcs {
		err := svc.Send(ctx, tplId, args, phones...)
		if err == nil {
			// 记录日志：发送成功
			log.Printf("SMS sent successfully using provider: %s", svc.GetProvider())
			return nil
		}
		// 记录日志：发送失败，尝试下一个提供商
		log.Printf("Failed to send SMS using provider: %s, error: %v", svc.GetProvider(), err)
	}
	// 所有提供商都尝试失败后返回错误
	return errors.New("all SMS providers failed")
}

func (Self *SMSFailoverService) SendV2(ctx context.Context, tplId string, args []string, phones ...string) error {
	// 原子地增加索引以循环遍历服务提供商
	idx := atomic.AddUint64(&Self.idx, 1)
	svcLen := uint64(len(Self.svcs))

	for i := idx; i < idx+svcLen; i++ {
		svc := Self.svcs[i%svcLen]
		err := svc.Send(ctx, tplId, args, phones...)
		if err == nil {
			log.Printf("SMS sent successfully using provider: %s", svc.GetProvider())
			return nil
		}
		log.Printf("Failed to send SMS using provider: %s, error: %v", svc.GetProvider(), err)
		// 这里要做好监控，因为一般来说都是应该发送成功才对的
		if errors.Is(err, context.DeadlineExceeded) ||
			errors.Is(err, context.Canceled) ||
			errors.Is(err, ratelimit.ErrIsLimited) {
			// 如果是上下文相关错误（如超时或取消）和触发了限流，直接返回错误
			return err
		}
	}

	return errors.New("all SMS providers failed")
}

func (Self *SMSFailoverService) GetProvider() sms.ProviderName {
	return sms.ProviderName("Failover")
}

func NewSMSFailoverService(smsSvcs ...sms.Service) *SMSFailoverService {
	return &SMSFailoverService{
		svcs: smsSvcs,
	}
}
